package com.ihr360.payroll.service.salarytasklet.step.processor;

import com.ihr360.alps.api.ServiceProvider;
import com.ihr360.commons.exception.Ihr360Exception;
import com.ihr360.commons.lang.CollectionUtils;
import com.ihr360.commons.lang.StringUtils;
import com.ihr360.commons.model.type.FieldSource;
import com.ihr360.commons.model.type.FormulaFrom;
import com.ihr360.expr.ExprException;
import com.ihr360.expr.core.GraphCycleException;
import com.ihr360.expr.engine.GraphTraversalListener;
import com.ihr360.expr.engine.VariableID;
import com.ihr360.job.core.annotation.BeforeProcess;
import com.ihr360.job.core.item.ItemProcessor;
import com.ihr360.job.core.repository.JobRepository;
import com.ihr360.job.core.scope.context.SimpleStepContext;
import com.ihr360.payroll.model.dto.salary.basecode.SalaryFieldType;
import com.ihr360.payroll.model.dto.salary.basecode.SalaryTaxType;
import com.ihr360.payroll.model.dto.salary.embeddable.SalaryFieldValue;
import com.ihr360.payroll.model.dto.salary.embeddable.SystemSalaryFieldValue;
import com.ihr360.payroll.model.dto.salary.meta.SalaryField;
import com.ihr360.payroll.model.dto.salary.meta.system.SystemSalaryField;
import com.ihr360.payroll.model.type.SalaryPhase;
import com.ihr360.payroll.service.salarytasklet.SalaryCalcJobConfig;
import com.ihr360.payroll.service.salarytasklet.SalaryCalcNamingUtils;
import com.ihr360.payroll.service.salarytasklet.expr.engine.SalaryDependencyEngine;
import com.ihr360.payroll.service.salarytasklet.expr.engine.SalaryExprEngineProvider;
import com.ihr360.payroll.service.salarytasklet.listener.ILoggerCallback;
import com.ihr360.payroll.service.salarytasklet.step.SalaryCalcContext;
import com.ihr360.payroll.service.salarytasklet.step.SalaryFieldDependencyChecker;
import com.ihr360.payroll.service.salarytasklet.step.utils.SalaryCodeUtils;
import com.ihr360.validation.ValidationUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author davidwei
 */
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class SalaryFieldDependencyProcessor implements ItemProcessor<SalaryCalcContext, SalaryDependencyEngine> {
    final Logger logger = LoggerFactory.getLogger(SalaryFieldDependencyProcessor.class.getName());
    SimpleStepContext simpleStepContext;

    SalaryDependencyEngine engine;

    JobRepository jobRepository;

    @Override
    public SalaryDependencyEngine process(SalaryCalcContext calcContext) throws Exception {
        /**
         * 是否是合并计税批次
         */
        boolean mergeTaxTerm = calcContext.payPeriodTerm.isMergeTaxTerm();
        /**
         * 合并计税依赖的批次id
         */
        Long basedOnTermId = calcContext.payPeriodTerm.getBasedOnTermId();
        ValidationUtils.rejectIfTrue(mergeTaxTerm && basedOnTermId == null, null, "无法获取合计计税的源数据");

        String planName = calcContext.payPeriodTerm.getBasedOnPlanName();
        if (mergeTaxTerm) {
            jobRepository.info(simpleStepContext, "[公式初始化]", String.format("此方案将与方案%s进行合并计税", planName));
        }
        if (StringUtils.isNotBlank(planName)) {
            planName = SalaryCalcNamingUtils.toQuotedVariableName(planName);
        }
        engine = calcContext.dependencyEngineHolder.getWrittenItem();
        if (engine == null) {
            engine = new SalaryDependencyEngine(new SalaryExprEngineProvider(calcContext.variableFetcher));
        }
        engine.setAutoCalculate(false);
        Map<String, SalaryField> salaryFields = calcContext.salaryFieldsHolder.getWrittenItem();
        try {
            SalaryFieldDependencyChecker.check(engine, salaryFields, mergeTaxTerm, new ILoggerCallback() {
                @Override
                public void trace(String title, String content) {
                    jobRepository.trace(simpleStepContext, title, content);
                }

                @Override
                public void info(String title, String content) {
                    jobRepository.info(simpleStepContext, title, content);
                }

                @Override
                public void warning(String title, String content) {
                    jobRepository.warning(simpleStepContext, title, content);
                }

                @Override
                public void error(String title, String content) {
                    jobRepository.error(simpleStepContext, title, content);
                }
            }, calcContext, planName);
        } catch (ExprException e) {
            Throwable cause = e.getCause();
            if (cause instanceof GraphCycleException) {
                GraphCycleException exception = (GraphCycleException) cause;
                Object[] args = exception.getArgs();
                if (args.length == 2) {
                    String variableId1 = args[0].toString();
                    String variableId2 = args[1].toString();
                    SalaryField field1 = salaryFields.get(variableId1);
                    if (field1 == null) {
                        variableId1 = calcContext.getSystemSalaryFieldName(variableId1);
                    } else {
                        variableId1 = field1.getName();
                    }
                    SalaryField field2 = salaryFields.get(variableId2);
                    if (field2 == null) {
                        variableId2 = calcContext.getSystemSalaryFieldName(variableId2);
                    } else {
                        variableId2 = field2.getName();
                    }
                    String content = String.format("薪资计算项目 %s和%s存在循环引用", variableId1, variableId2);
                    jobRepository.error(simpleStepContext, "[[公式初始化]]", content);
                    throw new Ihr360Exception(null, content);
                }
            }
        }

        return engine;
    }


    @BeforeProcess
    public void setSimpleStepContext(SalaryCalcContext calcContext, SimpleStepContext simpleStepContext) {
        this.simpleStepContext = simpleStepContext;
    }

    @Autowired
    public void setJobRepository(JobRepository jobRepository) {
        this.jobRepository = jobRepository;
    }
}